{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "1fd93c08-ac6b-4b52-b24c-da35b4ad2a5c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Python 3.12.7\n"
     ]
    }
   ],
   "source": [
    "!python --version"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "4b6ad720-c9d1-4ef8-9f95-df4d9f557b02",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 配置环境\n",
    "from langchain_community.document_loaders import PyPDFLoader # PDF文档提取\n",
    "from langchain_text_splitters import RecursiveCharacterTextSplitter # 文档拆分chunk\n",
    "from sentence_transformers import SentenceTransformer # 加载和使用Embedding模型\n",
    "import faiss # Faiss向量库\n",
    "import numpy as np # 处理嵌入向量数据，用于Faiss向量检索\n",
    "import dashscope #调用Qwen大模型\n",
    "from http import HTTPStatus #检查与Qwen模型HTTP请求状态\n",
    "\n",
    "import os # 引入操作系统库，后续配置环境变量与获得当前文件路径使用\n",
    "os.environ[\"TOKENIZERS_PARALLELISM\"] = \"false\" # 不使用分词并行化操作, 避免多线程或多进程环境中运行多个模型引发冲突或死锁\n",
    "\n",
    "# 设置Qwen系列具体模型及对应的调用API密钥，从阿里云百炼大模型服务平台获得\n",
    "qwen_model = \"qwen-turbo\"\n",
    "qwen_api_key = \"sk-cf34ad56322240188b73d2a0xxxx\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "90c3ed4a-3dd7-469a-a81f-67a9ae349215",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义文件向量\n",
    "def indexing_process(pdf_file, embedding_model):\n",
    "    \"\"\"\n",
    "    索引流程：加载PDF文件，并将其内容分割成小块，计算这些小块的嵌入向量并将其存储在FAISS向量数据库中。\n",
    "    :param pdf_file: PDF文件路径\n",
    "    :param embedding_model: 预加载的嵌入模型\n",
    "    :return: 返回Faiss嵌入向量索引和分割后的文本块原始内容列表\n",
    "    \"\"\"\n",
    "    # PyPDFLoader加载PDF文件，忽略图片提取\n",
    "    pdf_loader = PyPDFLoader(pdf_file, extract_images=False)\n",
    "    # 配置RecursiveCharacterTextSplitter分割文本块库参数，每个文本块的大小为512字符（非token），相邻文本块之间的重叠128字符（非token）\n",
    "    text_splitter = RecursiveCharacterTextSplitter(\n",
    "        chunk_size=512, chunk_overlap=128\n",
    "    )\n",
    "    # 加载PDF文档,提取所有页的文本内容\n",
    "    pdf_content_list = pdf_loader.load()\n",
    "    # 将每页的文本内容用换行符连接，合并为PDF文档的完整文本\n",
    "    pdf_text = \"\\n\".join([page.page_content for page in pdf_content_list])\n",
    "    print(f\"PDF文档的总字符数: {len(pdf_text)}\")\n",
    "\n",
    "    # 将PDF文档文本分割成文本块Chunk\n",
    "    chunks = text_splitter.split_text(pdf_text)\n",
    "    print(f\"分割的文本Chunk数量: {len(chunks)}\")\n",
    "\n",
    "    # 文本块转化为嵌入向量列表，normalize_embeddings表示对嵌入向量进行归一化，用于准确计算相似度\n",
    "    embeddings = []\n",
    "    for chunk in chunks:\n",
    "        embedding = embedding_model.encode(chunk, normalize_embeddings=True)\n",
    "        embeddings.append(embedding)\n",
    "\n",
    "    print(\"文本块Chunk转化为嵌入向量完成\")\n",
    "\n",
    "    # 将嵌入向量列表转化为numpy数组，Faiss向量库操作需要numpy数组输入\n",
    "    embeddings_np = np.array(embeddings)\n",
    "\n",
    "    # 获取嵌入向量的维度（每个向量的长度），shape返回数组的形状，第二个元素是列数（即每个嵌入向量的维度）\n",
    "    dimension = embeddings_np.shape[1]\n",
    "\n",
    "    # 使用余弦相似度创建FAISS索引，创建了一个基于内积的索引对象 (IndexFlatIP)，用于执行相似度搜索\n",
    "    # IndexFlatIP 表示这是一个“平坦”的内积索引，它会存储所有向量并在查询时遍历整个数据库来计算内积，从而找到最相似的向量。这种方式简单直接，但在非常大的数据集上可能不是最高效的。\n",
    "    index = faiss.IndexFlatIP(dimension)\n",
    "    # 将所有的嵌入向量添加到FAISS索引中，后续可以用来进行相似性检索\n",
    "    index.add(embeddings_np)\n",
    "\n",
    "    print(\"索引过程完成.\")\n",
    "\n",
    "    return index, chunks"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "6a6814b0-77cc-48e0-8f1e-1cb4bd1a89da",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 加载向量模型\n",
    "def load_embedding_model():\n",
    "    \"\"\"\n",
    "    加载bge-small-zh-v1.5模型\n",
    "    :return: 返回加载的bge-small-zh-v1.5模型\n",
    "    \"\"\"\n",
    "    print(f\"加载Embedding模型中\")\n",
    "    # SentenceTransformer读取绝对路径下的bge-small-zh-v1.5模型，非下载\n",
    "    embedding_model = SentenceTransformer(os.path.abspath('/Users/edward/model/bge-small-zh-v1.5'))\n",
    "    print(f\"bge-small-zh-v1.5模型最大输入长度: {embedding_model.max_seq_length}\")\n",
    "    return embedding_model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "be72b232-f705-4395-9318-6749a1ce0d8b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 从数据库中检索于问题相似的内容信息，并返回\n",
    "def retrieval_process(query, index, chunks, embedding_model, top_k=3):\n",
    "    \"\"\"\n",
    "    检索流程：将用户查询Query转化为嵌入向量，并在Faiss索引中检索最相似的前k个文本块。\n",
    "    :param query: 用户查询语句\n",
    "    :param index: 已建立的Faiss向量索引\n",
    "    :param chunks: 原始文本块内容列表\n",
    "    :param embedding_model: 预加载的嵌入模型\n",
    "    :param top_k: 返回最相似的前K个结果\n",
    "    :return: 返回最相似的文本块及其相似度得分\n",
    "    \"\"\"\n",
    "    # 将查询转化为嵌入向量，normalize_embeddings表示对嵌入向量进行归一化\n",
    "    query_embedding = embedding_model.encode(query, normalize_embeddings=True)\n",
    "    # 将嵌入向量转化为numpy数组，Faiss索引操作需要numpy数组输入\n",
    "    query_embedding = np.array([query_embedding])\n",
    "\n",
    "    # 在 Faiss 索引中使用 query_embedding 进行搜索，检索出最相似的前 top_k 个结果。\n",
    "    # 返回查询向量与每个返回结果之间的相似度得分（在使用余弦相似度时，值越大越相似）排名列表distances，最相似的 top_k 个文本块在原始 chunks 列表中的索引indices。\n",
    "    distances, indices = index.search(query_embedding, top_k)\n",
    "\n",
    "    print(f\"查询语句: {query}\")\n",
    "    print(f\"最相似的前{top_k}个文本块:\")\n",
    "\n",
    "    # 输出查询出的top_k个文本块及其相似度得分\n",
    "    results = []\n",
    "    for i in range(top_k):\n",
    "        # 获取相似文本块的原始内容\n",
    "        result_chunk = chunks[indices[0][i]]\n",
    "        print(f\"文本块 {i}:\\n{result_chunk}\") \n",
    "\n",
    "        # 获取相似文本块的相似度得分\n",
    "        result_distance = distances[0][i]\n",
    "        print(f\"相似度得分: {result_distance}\\n\")\n",
    "\n",
    "        # 将相似文本块存储在结果列表中\n",
    "        results.append(result_chunk)\n",
    "\n",
    "    print(\"检索过程完成.\")\n",
    "    return results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "e5bfb6ad-32d9-4057-80e3-622d00b0428d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 生成提示文本\n",
    "def generate_process(query, chunks):\n",
    "    \"\"\"\n",
    "    生成流程：调用Qwen大模型云端API，根据查询和文本块生成最终回复。\n",
    "    :param query: 用户查询语句\n",
    "    :param chunks: 从检索过程中获得的相关文本块上下文chunks\n",
    "    :return: 返回生成的响应内容\n",
    "    \"\"\"\n",
    "    # 设置Qwen系列具体模型及对应的调用API密钥，从阿里云大模型服务平台百炼获得\n",
    "    llm_model = qwen_model\n",
    "    dashscope.api_key = qwen_api_key\n",
    "\n",
    "    # 构建参考文档内容，格式为“参考文档1: \\n 参考文档2: \\n ...”等\n",
    "    context = \"\"\n",
    "    for i, chunk in enumerate(chunks):\n",
    "        context += f\"参考文档{i+1}: \\n{chunk}\\n\\n\"\n",
    "\n",
    "    # 构建生成模型所需的Prompt，包含用户查询和检索到的上下文\n",
    "    prompt = f\"根据参考文档回答问题：{query}\\n\\n{context}\"\n",
    "    print(f\"生成模型的Prompt: {prompt}\")\n",
    "\n",
    "    # 准备请求消息，将prompt作为输入\n",
    "    messages = [{'role': 'user', 'content': prompt}]\n",
    "\n",
    "    # 调用大模型API云服务生成响应\n",
    "    try:\n",
    "        responses = dashscope.Generation.call(\n",
    "            model = llm_model,\n",
    "            messages=messages,\n",
    "            result_format='message',  # 设置返回格式为\"message\"\n",
    "            stream=True,              # 启用流式输出\n",
    "            incremental_output=True   # 获取流式增量输出\n",
    "        )\n",
    "        # 初始化变量以存储生成的响应内容\n",
    "        generated_response = \"\"\n",
    "        print(\"生成过程开始:\")\n",
    "        # 逐步获取和处理模型的增量输出\n",
    "        for response in responses:\n",
    "            if response.status_code == HTTPStatus.OK:\n",
    "                content = response.output.choices[0]['message']['content']\n",
    "                generated_response += content\n",
    "                print(content, end='')  # 实时输出模型生成的内容\n",
    "            else:\n",
    "                print(f\"请求失败: {response.status_code} - {response.message}\")\n",
    "                return None  # 请求失败时返回 None\n",
    "        print(\"\\n生成过程完成.\")\n",
    "        return generated_response\n",
    "    except Exception as e:\n",
    "        print(f\"大模型生成过程中发生错误: {e}\")\n",
    "        return None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "8bd6da7a-eb6c-42c1-94fd-15227e7d34b9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "RAG过程开始.\n",
      "加载Embedding模型中\n",
      "bge-small-zh-v1.5模型最大输入长度: 512\n",
      "PDF文档的总字符数: 9163\n",
      "分割的文本Chunk数量: 24\n",
      "文本块Chunk转化为嵌入向量完成\n",
      "索引过程完成.\n",
      "查询语句: 下面报告中涉及了哪几个行业的案例以及总结各自面临的挑战？\n",
      "最相似的前3个文本块:\n",
      "文本块 0:\n",
      "面的数字化转型。\n",
      "2.3.2 面临的挑战\n",
      "在数字化转型之前，金融业案例中银行面临以下主要挑战：客户服务模式过时，主要依赖实\n",
      "体网点，导致服务效率低、客户体验差；金融科技企业带来巨大竞争压力，凭借创新技术和\n",
      "便捷服务吸引大量客户，尤其是年轻一代；数据孤岛和风险管理滞后，各业务部门缺乏数据\n",
      "共享机制，导致信息无法整合，风险管理效率低。\n",
      "2.3.3 数字化转型解决方案\n",
      "为应对金融业案例挑战，银行实施了多方面的数字化转型措施：首先，构建数字化银行平台，\n",
      "推出移动银行应用、在线服务、虚拟客服和智能理财顾问，显著提升了服务便捷性和客户满\n",
      "意度；其次，引入人工智能和大数据分析技术，通过个性化金融产品推荐和实时风险监控，\n",
      "提升客户服务质量和风险管理能力。\n",
      "相似度得分: 0.5915017127990723\n",
      "\n",
      "文本块 1:\n",
      "导致效率低且易出错；供应链复杂，涉及多个国家和地区，信息传递不及时，造成库存管理\n",
      "困难，甚至存在供应链断裂的风险；客户需求变化快，传统大规模生产方式无法满足市场对\n",
      "个性化定制产品的需求。\n",
      "2.1.3 数字化转型解决方案\n",
      "为了应对制造业上述挑战，公司通过以下步骤进行数字化转型：首先，引入工业 4.0 技术，\n",
      "包括物联网（IoT）、人工智能（AI）、大数据分析和机器人自动化，以优化生产线；其次，\n",
      "构建基于云计算的智能供应链管理系统，实现供应链的端到端可视化管理。\n",
      "2.2 案例二：零售业的数字化转型\n",
      "2.2.1 公司背景\n",
      "零售业案例讲述了一家全球知名的快时尚服装零售企业，面对电子商务的崛起和消费者购物\n",
      "行为的快速变化，传统零售模式受到巨大挑战。为保持市场竞争力并满足消费者日益增长的\n",
      "数字化需求，公司决定实施全面的数字化转型战略。\n",
      "2.2.2 面临的挑战\n",
      "在数字化转型之前，零售业案例的公司面临以下挑战：线上线下渠道割裂，导致库存管理不\n",
      "统一、客户体验不一致，难以提供无缝购物体验；数据利用率低，尽管拥有大量消费者和销\n",
      "售数据，但缺乏先进的数据分析工具，未能转化为可操作的商业洞察。\n",
      "2.2.3 数字化转型解决方案\n",
      "相似度得分: 0.572852373123169\n",
      "\n",
      "文本块 2:\n",
      "统一、客户体验不一致，难以提供无缝购物体验；数据利用率低，尽管拥有大量消费者和销\n",
      "售数据，但缺乏先进的数据分析工具，未能转化为可操作的商业洞察。\n",
      "2.2.3 数字化转型解决方案\n",
      "为了解决零售业案例的线上线下渠道割裂、数据利用率低、供应链效率低下和客户体验滞后\n",
      "等问题，公司实施了一系列数字化转型措施：首先，构建全渠道零售平台，实现线上与线下\n",
      "购物渠道的无缝整合，提升顾客的便利性和满意度；其次，引入大数据和人工智能驱动的分\n",
      "析平台，精准预测需求、优化库存，并提供个性化产品推荐和营销活动。\n",
      "2.3 案例三：金融业的数字化转型\n",
      "2.3.1 公司背景\n",
      "金融业案例中的金融机构是一家全球知名的银行，成立已有百年历史。随着金融科技\n",
      "（FinTech）的迅速发展以及消费者对在线金融服务需求的增加，传统银行业务模式面临前\n",
      "所未有的挑战。为了保持市场竞争力并满足客户日益增长的数字化需求，该银行决定开展全\n",
      "面的数字化转型。\n",
      "2.3.2 面临的挑战\n",
      "在数字化转型之前，金融业案例中银行面临以下主要挑战：客户服务模式过时，主要依赖实\n",
      "体网点，导致服务效率低、客户体验差；金融科技企业带来巨大竞争压力，凭借创新技术和\n",
      "相似度得分: 0.5637903213500977\n",
      "\n",
      "检索过程完成.\n",
      "生成模型的Prompt: 根据参考文档回答问题：下面报告中涉及了哪几个行业的案例以及总结各自面临的挑战？\n",
      "\n",
      "参考文档1: \n",
      "面的数字化转型。\n",
      "2.3.2 面临的挑战\n",
      "在数字化转型之前，金融业案例中银行面临以下主要挑战：客户服务模式过时，主要依赖实\n",
      "体网点，导致服务效率低、客户体验差；金融科技企业带来巨大竞争压力，凭借创新技术和\n",
      "便捷服务吸引大量客户，尤其是年轻一代；数据孤岛和风险管理滞后，各业务部门缺乏数据\n",
      "共享机制，导致信息无法整合，风险管理效率低。\n",
      "2.3.3 数字化转型解决方案\n",
      "为应对金融业案例挑战，银行实施了多方面的数字化转型措施：首先，构建数字化银行平台，\n",
      "推出移动银行应用、在线服务、虚拟客服和智能理财顾问，显著提升了服务便捷性和客户满\n",
      "意度；其次，引入人工智能和大数据分析技术，通过个性化金融产品推荐和实时风险监控，\n",
      "提升客户服务质量和风险管理能力。\n",
      "\n",
      "参考文档2: \n",
      "导致效率低且易出错；供应链复杂，涉及多个国家和地区，信息传递不及时，造成库存管理\n",
      "困难，甚至存在供应链断裂的风险；客户需求变化快，传统大规模生产方式无法满足市场对\n",
      "个性化定制产品的需求。\n",
      "2.1.3 数字化转型解决方案\n",
      "为了应对制造业上述挑战，公司通过以下步骤进行数字化转型：首先，引入工业 4.0 技术，\n",
      "包括物联网（IoT）、人工智能（AI）、大数据分析和机器人自动化，以优化生产线；其次，\n",
      "构建基于云计算的智能供应链管理系统，实现供应链的端到端可视化管理。\n",
      "2.2 案例二：零售业的数字化转型\n",
      "2.2.1 公司背景\n",
      "零售业案例讲述了一家全球知名的快时尚服装零售企业，面对电子商务的崛起和消费者购物\n",
      "行为的快速变化，传统零售模式受到巨大挑战。为保持市场竞争力并满足消费者日益增长的\n",
      "数字化需求，公司决定实施全面的数字化转型战略。\n",
      "2.2.2 面临的挑战\n",
      "在数字化转型之前，零售业案例的公司面临以下挑战：线上线下渠道割裂，导致库存管理不\n",
      "统一、客户体验不一致，难以提供无缝购物体验；数据利用率低，尽管拥有大量消费者和销\n",
      "售数据，但缺乏先进的数据分析工具，未能转化为可操作的商业洞察。\n",
      "2.2.3 数字化转型解决方案\n",
      "\n",
      "参考文档3: \n",
      "统一、客户体验不一致，难以提供无缝购物体验；数据利用率低，尽管拥有大量消费者和销\n",
      "售数据，但缺乏先进的数据分析工具，未能转化为可操作的商业洞察。\n",
      "2.2.3 数字化转型解决方案\n",
      "为了解决零售业案例的线上线下渠道割裂、数据利用率低、供应链效率低下和客户体验滞后\n",
      "等问题，公司实施了一系列数字化转型措施：首先，构建全渠道零售平台，实现线上与线下\n",
      "购物渠道的无缝整合，提升顾客的便利性和满意度；其次，引入大数据和人工智能驱动的分\n",
      "析平台，精准预测需求、优化库存，并提供个性化产品推荐和营销活动。\n",
      "2.3 案例三：金融业的数字化转型\n",
      "2.3.1 公司背景\n",
      "金融业案例中的金融机构是一家全球知名的银行，成立已有百年历史。随着金融科技\n",
      "（FinTech）的迅速发展以及消费者对在线金融服务需求的增加，传统银行业务模式面临前\n",
      "所未有的挑战。为了保持市场竞争力并满足客户日益增长的数字化需求，该银行决定开展全\n",
      "面的数字化转型。\n",
      "2.3.2 面临的挑战\n",
      "在数字化转型之前，金融业案例中银行面临以下主要挑战：客户服务模式过时，主要依赖实\n",
      "体网点，导致服务效率低、客户体验差；金融科技企业带来巨大竞争压力，凭借创新技术和\n",
      "\n",
      "\n",
      "生成过程开始:\n",
      "请求失败: 401 - Invalid API-key provided.\n",
      "RAG过程结束.\n"
     ]
    }
   ],
   "source": [
    "# 测试\n",
    "def main():\n",
    "    print(\"RAG过程开始.\")\n",
    "\n",
    "    query=\"下面报告中涉及了哪几个行业的案例以及总结各自面临的挑战？\"\n",
    "    embedding_model = load_embedding_model()\n",
    "\n",
    "    # 索引流程：加载PDF文件，分割文本块，计算嵌入向量，存储在FAISS向量库中（内存）\n",
    "    index, chunks = indexing_process('~/Documents/项目/rag_app/test_lesson2.pdf', embedding_model)\n",
    "\n",
    "    # 检索流程：将用户查询转化为嵌入向量，检索最相似的文本块\n",
    "    retrieval_chunks = retrieval_process(query, index, chunks, embedding_model)\n",
    "\n",
    "    # 生成流程：调用Qwen大模型生成响应\n",
    "    generate_process(query, retrieval_chunks)\n",
    "\n",
    "    print(\"RAG过程结束.\")\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
